from visual import *

### EFieldBuilder.py
### Interactive E-Field Builder (requires VPython)
### Rob Salgado
### salgado@physics.syr.edu     http://physics.syr.edu/~salgado/
### v0.9990 2005-03-08 tested on Windows 2000
###         with Python-2.3.3.exe and VPython-2003-10-05.exe
###         * added fixedwidth to arrows
###         * hide small vectors
### v0.9972 2002-01-10 tested on Windows 2000
###         with Python-2.2.exe and VPython-2001-12-31.exe
###         * used .click instead of .button
###         * modified the drag flag ####
###
### v0.997  2001-11-07 tested on Windows 2000
###         with Python-2.1.1.exe and VPython-2001-10-31.exe
###         * uses specified shaftwidth for the arrows
###         * removes the dependence on fpformat 
### v0.996  2001-11-03 tested on Windows 2000
###         with Python-2.1.1.exe and VPython-2001-10-31.exe
### v0.99   2001-10-23 tested on Windows 2000
###         with Python-2.1.1.exe and VPython-2001-10-08.exe



print"""
Interactive E-Field Builder (v0.9990)
Rob Salgado (salgado@physics.syr.edu)

This VPython program allows you to position point charges in space.
The Electric Field due to those charges is computed dynamically.
Use the control panel to change the sign and magnitude of the
next charge to be placed.
In 'Create' mode:"
    Left-Clicking once on an empty location will place a new charge there.
    Left-Clicking-and-Dragging on an existing charge will reposition it.
In 'Edit' mode:
    Left-Clicking once on an existing charge will change its properties.
In 'Copy' mode:
    Left-Clicking once on an existing charge will copy its settings.
To delete an existing charge:
    First, select the 'Edit' mode and set the charge to 0.0.
    Then, select the charge to be deleted.
Wheel-Click-and-Drag or Left-and-Right-Click-and-Drag to zoom in and out.
Right-Click-and-Drag to rotate about the origin.
"""


scene2 = display(title='set Q',
     x=0,y=0,width=100, height=100,
     center=(0,0,0), background=(0.25,0.25,0.25))

setQ = sphere(pos=(1,0,0), radius=1,  color=color.blue,Q=1.0 )
incQ=cylinder(pos=(-1.5,0.5,0), axis=(0,1,0), radius=0.2,color=color.blue)
decQ=cylinder(pos=(-1.5,-0.5,0), axis=(0,-1,0), radius=0.2,color=color.red)
#labQ=label(pos=(1,0,0), text=fpformat.fix(setQ.Q, 1),opacity=0.,box=0)
labQ=label(pos=(1,0,0), text="%.1f" % setQ.Q,opacity=0.,box=0)
modestate=['create','edit','copy']
modeboxQ=box(pos=(-1.5,0,0),color=color.black)
modeQ=label(pos=(-1.5,0,0), text='create',box=0,mode=0)

scene2.visible=1
scene2.userspin=0
scene2.userzoom=0
scene2.autoscale=0

scene = display(title="E-Field Builder (Rob Salgado)", x=100,y=100)
scene.select() 
scene.newzoom=1


scale=0.2
step=1.
S=3.
drag=0

zeroV=vector(0,0,0)

charges=[]
Efield=[]
for x in arange(-S,S,step):
    for y in arange(-S,S,step):
        for z in arange(-S,S,step):
            vec=arrow(pos=(x,y,z),axis=(0,0,0),shaftwidth=scale/4.,fixedwidth=1)
            Efield.append(vec)

scene.autoscale=0
scene.range=(5,5,5)

def EFieldAt(p):
    E = zeroV
    for c in charges:
        delta=p-c.pos
        if mag(delta)<2e-10:
            print "small"
            print mag(delta)
        else:
            E = E + (delta) * c.Q / mag(delta)**3
    return scale*E

def updateEField():
    for v in Efield:
        v.axis=EFieldAt(v.pos)
        if mag2(v.axis)<2e-3:   #hide small vectors
            v.axis=zeroV

def update2():
    modeQ.text=modestate[modeQ.mode]
    if modeQ.mode==0:
        scene2.background=(0.25,0.25,0.25)
    else:
        scene2.background=(1,1,1)

    if setQ.Q>0.0:
        setQ.color=color.blue
    elif setQ.Q<0.0:
        setQ.color=color.red
    else:
        setQ.color=color.green
    labQ.text="%.1f" % setQ.Q 
    
n=None
    
while 1:
    if scene2.mouse.clicked:
        m = scene2.mouse.getclick()
        newPick2=scene2.mouse.pick
        if newPick2==incQ:
            setQ.Q=setQ.Q+0.5
        elif newPick2==decQ:
            setQ.Q=setQ.Q-0.5
        elif newPick2==modeboxQ:
            modeQ.mode=(modeQ.mode+1)%3
        update2()


    if scene.mouse.clicked:
        m = scene.mouse.getclick()
        newPick=scene.mouse.pick
        if m.click == "none":
            drag=0
            if n!=None:
                    if n.Q>0.0:
                        n.color=color.blue
                    elif n.Q<0.0:
                        n.color=color.red
                    else:
                        n.color=color.green
            print "Release"
        elif m.click == "left":
            if newPick==None:#if vacant, make new charge
               newChg=sphere(pos=m.pos, radius=abs(setQ.Q)/5.0, color=setQ.color, Q=setQ.Q)
               if setQ.Q==0:#give neutral charge a nonzero radius
                   newChg.radius=1.0/5.0
               charges.append(newChg)
               updateEField()
               n=newChg
               #n.green=(1-n.green)
               drag=0 ####
               if drag==0:
                   if n.Q>0.0:
                        n.color=color.blue
                   elif n.Q<0.0:
                        n.color=color.red
                   else:
                        n.color=color.green
            else:
               n=newPick
               if modeQ.mode==0:#toggle drag state
                    drag=(drag+1)%2 ####
                    if drag==0:
                        if n.Q>0.0:
                            n.color=color.blue
                        elif n.Q<0.0:
                            n.color=color.red
                        else:
                            n.color=color.green
                    else:
                        n.green=(1-n.green)
               elif modeQ.mode==1:#update charge
                    if setQ.Q !=0.0:
                        n.radius=abs(setQ.Q)/5.0
                        n.color=setQ.color
                        n.Q=setQ.Q
                    else:
                        n.visible=0
                        charges.remove(n)
                    modeQ.mode=0
                    update2()
                    updateEField()
               elif modeQ.mode==2:#clone existing charge
                    setQ.Q=n.Q
                    setQ.color=n.color
                    labQ.text= "%.1f" % setQ.Q
                    modeQ.mode=0
                    update2()
        elif m.click == "right":
            print "Right"
    if drag==1:
        #print "Drag ",
        #print scene.mouse.button
        if n!=None:
            n.pos=scene.mouse.pos
            updateEField() 